/// <reference path="../types.d.ts" />
import { deepEqual, checkOptions } from "./utils"
export default class {
  constructor(option: ExposureOptions = {}) {
    const { once, exposureThreshold, callback, root, rootMargin, threshold } = option
    this.defOnce = once ?? false
    this.defThreshold = threshold ?? 0
    this.defExposureThreshold = exposureThreshold ?? 0
    this.defCallback = callback
    this.myExposure = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
      entries.forEach(this.exposureHandle)
    }, { root, rootMargin, threshold })
  }
  private defOnce: boolean = false
  private defThreshold: number | number[] = 0
  private defExposureThreshold: number = 0
  private defCallback?: (visible: boolean, observe: IntersectionObserverEntry) => void
  private weakMap: WeakMap<Element, ExposureEntry> = new WeakMap()
  private myExposure: IntersectionObserver
  private checkVisible(isIntersecting: boolean, intersectionRatio: number) {
    let visibleState = isIntersecting
    if (typeof this.defThreshold === "number") {
      visibleState = isIntersecting && intersectionRatio >= this.defThreshold
    } else if (Array.isArray(this.defThreshold)) {
      visibleState = this.defThreshold.some(threshold => isIntersecting && intersectionRatio >= threshold)
    }
    return visibleState
  }
  private async exposureHandle(entry: IntersectionObserverEntry) {
    const { target, isIntersecting, intersectionRatio } = entry
    const visibleState = this.checkVisible(isIntersecting, intersectionRatio)
    if (this.weakMap.has(target)) {
      const { once, exposureThreshold, exposureTimer, callback } = this.weakMap.get(target) as ExposureEntry
      this.weakMap.set(target, { state: visibleState, once, exposureThreshold, exposureTimer, callback })
      if (callback) {
        if ((exposureThreshold !== undefined && exposureThreshold > 0) || (exposureThreshold === undefined && this.defExposureThreshold > 0)) {
          clearTimeout(this.weakMap.get(target)!.exposureTimer)
          typeof this.weakMap.get(target)!.exposureReject === 'function' && this.weakMap.get(target)!.exposureReject!()
          try {
            await new Promise((resolve, reject) => {
              this.weakMap.get(target)!.exposureTimer = setTimeout(resolve, exposureThreshold ?? this.defExposureThreshold);
              this.weakMap.get(target)!.exposureReject = reject
            })
          } catch (error) {
            console.warn(`节点展示时长低于${exposureThreshold ?? this.defExposureThreshold}ms，本次曝光已被中止`);
            return
          }
        }
        callback ? callback(visibleState, entry) : this.defCallback?.(visibleState, entry)
        // 展示仅触发一次
        if ((this.defOnce || once) && visibleState) {
          this.removeEl(target)
        }
      }
    } else {
      this.removeEl(target)
    }
  }
  addEl(el: Element, val: any, modifiers: any): void {
    const { exposureThreshold, callback } = checkOptions(val)
    const { once } = modifiers
    this.weakMap.set(el, { state: false, once, exposureThreshold, callback })
    this.myExposure.observe(el)
  }
  updateEl(el: Element, newVal: any, oldVal: any, modifiers: any) {
    if (this.weakMap.has(el)) {
      const { once } = modifiers
      this.weakMap.get(el)!.once = once
      if (!deepEqual(newVal, oldVal)) {
        if (newVal === false) {
          this.removeEl(el)
        } else {
          const { exposureThreshold, callback } = checkOptions(newVal)
          this.weakMap.get(el)!.once = once
          this.weakMap.get(el)!.exposureThreshold = exposureThreshold
          this.weakMap.get(el)!.callback = callback
        }
      }
    } else if (newVal !== false) {
      this.addEl(el, newVal, modifiers)
    }
  }
  removeEl(el: Element): void {
    this.weakMap.delete(el)
    this.myExposure.unobserve(el)
  }
  disconnect(): void {
    this.weakMap = new WeakMap()
    this.myExposure.disconnect()
  }
}